/***************************************************************************
 *   Copyleft 2006-2007 by												   *
 *	 Iwanicki Krzysztof													   *
 *   <gamma.mini.pw.edu.pl/~iwanickik>									   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 ***************************************************************************/
#include "./BezierCurve.h"

/**************************************************************
* Constructors                                                *
***************************************************************/
BezierCurve::BezierCurve(): PointsSet() {
	mPolygonColor = RGB(0, 255, 127);
	mIsPolygonVisible = true;
	mArePointsVisible = true;
}

BezierCurve::BezierCurve(cBool arePointsVisible, cBool isPolygonVisible, cWinColor color): PointsSet() {
	mPolygonColor = color;
	mIsPolygonVisible = isPolygonVisible;
	mArePointsVisible = arePointsVisible;
}
    
/**************************************************************
* Operators                                                   *
***************************************************************/


/**************************************************************
* Other helpful functions                                     *
***************************************************************/
/*virtual*/ Point4D BezierCurve::GetCurvePoint(cInt iIndex) {
	return mPoints[iIndex].mPoint;
}

/*virtual*/ cInt BezierCurve::GetCurveNrOfPoints() {
	return PointsSet::GetNrOfPoints();
}

cVoid BezierCurve::RenderPolygon(Renderer& renderer, Matrix4D& mProjectionMatrix, ProjectionPlane& plane,
								 cWinColor color) {
	cInt iHalfWidth = renderer.GetWidth() >> 1, iHalfHeight = renderer.GetHeight() >> 1;

	Point4D *points = new Point4D[mNrOfPoints];
	Point4D a, b;
	for (cInt i = 0; i < mNrOfPoints; i++) {
		points[i] = mProjectionMatrix * mPoints[i].mPoint;
	}
	for (cInt i = 0; i < mNrOfPoints - 1; i++) {
		a = points[i];
		b = points[i + 1];
		if (Clip(a, b, plane))	{
			a.mX = (a.mX / a.mW) + iHalfWidth; 
			a.mY = (a.mY / -a.mW) + iHalfHeight;
			b.mX = (b.mX / b.mW) + iHalfWidth; 
			b.mY = (b.mY / -b.mW) + iHalfHeight;
			renderer.DrawROPLine(a, b, color);
		}
	}
}

cVoid BezierCurve::RenderPolygon(Renderer& renderer, Matrix4D& mProjectionMatrix, ProjectionPlane& plane,
								 cWinColor color, Point4D *tab, cInt iSize) {
	cInt iHalfWidth = renderer.GetWidth() >> 1, iHalfHeight = renderer.GetHeight() >> 1;
	Point4D a, b;

	for (cInt i = 0; i < iSize - 1; i++) {
		a = tab[i];
		b = tab[i + 1];
		if (Clip(a, b, plane))	{
			a.mX = (a.mX / a.mW) + iHalfWidth; 
			a.mY = (a.mY / -a.mW) + iHalfHeight;
			b.mX = (b.mX / b.mW) + iHalfWidth; 
			b.mY = (b.mY / -b.mW) + iHalfHeight;
			renderer.DrawROPLine(a, b, color);
		}
	}
}

cVoid BezierCurve::Render(Renderer& renderer, Matrix4D& matrix, ProjectionPlane& plane, Point4D *tab, cInt iSize) {
	cInt iHalfWidth = renderer.GetWidth() >> 1, iHalfHeight = renderer.GetHeight() >> 1;
	cInt i;

	for (i = 0; i <= iSize - 4; i += 3) {
		CubicBezier(renderer, plane, mPolygonColor, tab[i], tab[i + 1], tab[i + 2], tab[i + 3]);
	}
	if (i <= iSize - 3) {
		QuadricBezier(renderer, plane, mPolygonColor, tab[i], tab[i + 1], tab[i + 2]);
	} else if (i <= iSize - 2) {
		if (Clip(tab[i], tab[i + 1], plane))	{
			tab[i].mX = (tab[i].mX / tab[i].mW) + iHalfWidth;
			tab[i].mY = (tab[i].mY / -tab[i].mW) + iHalfHeight;
			tab[i + 1].mX = (tab[i + 1].mX / tab[i + 1].mW) + iHalfWidth;
			tab[i + 1].mY = (tab[i + 1].mY / -tab[i + 1].mW) + iHalfHeight;
			renderer.DrawROPLine(tab[i], tab[i + 1], mPolygonColor);
		}
	}
	if (mIsPolygonVisible) {
		RenderPolygon(renderer, matrix, plane, mColor, tab, iSize);
	}
}

cVoid BezierCurve::Render(Renderer& renderer, Matrix4D& matrix, ProjectionPlane& plane, cWinColor color, Point4D *tab, cInt iSize) {
	cInt iHalfWidth = renderer.GetWidth() >> 1, iHalfHeight = renderer.GetHeight() >> 1;
	cInt i;

	for (i = 0; i <= iSize - 4; i += 3) {
		CubicBezier(renderer, plane, color, tab[i], tab[i + 1], tab[i + 2], tab[i + 3]);
	}
	if (i <= iSize - 3) {
		QuadricBezier(renderer, plane, color, tab[i], tab[i + 1], tab[i + 2]);
	} else if (i <= iSize - 2) {
		if (Clip(tab[i], tab[i + 1], plane))	{
			tab[i].mX = (tab[i].mX / tab[i].mW) + iHalfWidth;
			tab[i].mY = (tab[i].mY / -tab[i].mW) + iHalfHeight;
			tab[i + 1].mX = (tab[i + 1].mX / tab[i + 1].mW) + iHalfWidth;
			tab[i + 1].mY = (tab[i + 1].mY / -tab[i + 1].mW) + iHalfHeight;
			renderer.DrawROPLine(tab[i], tab[i + 1], color);
		}
	}
	if (mIsPolygonVisible) {
		RenderPolygon(renderer, matrix, plane, color, tab, iSize);
	}
}


/*virtual*/ cVoid BezierCurve::Render(Renderer& renderer, Matrix4D& matrix, ProjectionPlane& plane) {
	if (mArePointsVisible) {
		PointsSet::Render(renderer, matrix, plane);
	}
	cInt iHalfWidth = renderer.GetWidth() >> 1, iHalfHeight = renderer.GetHeight() >> 1;
	cInt i;
	cInt size = mNrOfPoints;
	Point4D *tab = new Point4D[size];

	for (i = 0; i < size; i++) {
		tab[i] = matrix * mPoints[i].mPoint;
	}
	for (i = 0; i <= size - 4; i += 3) {
		CubicBezier(renderer, plane, mPolygonColor, tab[i], tab[i + 1], tab[i + 2], tab[i + 3]);
	}
	if (i <= size - 3) {
		QuadricBezier(renderer, plane, mPolygonColor, tab[i], tab[i + 1], tab[i + 2]);
	} else if (i <= size - 2) {
		if (Clip(tab[i], tab[i + 1], plane))	{
			tab[i].mX = (tab[i].mX / tab[i].mW) + iHalfWidth;
			tab[i].mY = (tab[i].mY / -tab[i].mW) + iHalfHeight;
			tab[i + 1].mX = (tab[i + 1].mX / tab[i + 1].mW) + iHalfWidth;
			tab[i + 1].mY = (tab[i + 1].mY / -tab[i + 1].mW) + iHalfHeight;
			renderer.DrawROPLine(tab[i], tab[i + 1], mPolygonColor);
		}
	}
	if (mIsPolygonVisible) {
		RenderPolygon(renderer, matrix, plane, RGB(255, 0, 0));
	}

	delete []tab;
}

/*virtual*/ cVoid BezierCurve::Render(Renderer& renderer, Matrix4D& matrix, ProjectionPlane& plane, cWinColor color) {
	if (mArePointsVisible) {
		PointsSet::Render(renderer, matrix, plane, color);
	}
	cInt i;
	cInt size = mNrOfPoints;
	Point4D *tab = new Point4D[size];
	cInt iHalfWidth = renderer.GetWidth() >> 1, iHalfHeight = renderer.GetHeight() >> 1;

	for (i = 0; i < size; i++) {
		tab[i] = matrix * mPoints[i].mPoint;
	}

	for (i = 0; i <= size - 4; i += 3) {
		CubicBezier(renderer, plane, color, tab[i], tab[i + 1], tab[i + 2], tab[i + 3]);
	}

	if (i <= size - 3) {
		QuadricBezier(renderer, plane, color, tab[i], tab[i + 1], tab[i + 2]);
	} else if (i <= size - 2) {
		if (Clip(tab[i], tab[i + 1], plane))	{
			tab[i].mX = (tab[i].mX / tab[i].mW) + iHalfWidth;
			tab[i].mY = (tab[i].mY / -tab[i].mW) + iHalfHeight;
			tab[i + 1].mX = (tab[i + 1].mX / tab[i + 1].mW) + iHalfWidth;
			tab[i + 1].mY = (tab[i + 1].mY / -tab[i + 1].mW) + iHalfHeight;
			renderer.DrawROPLine(tab[i], tab[i + 1], color);
		}
	}
	if (mIsPolygonVisible) {
		RenderPolygon(renderer, matrix, plane, color);
	}

	delete []tab;
}

Point4D BezierCurve::CalculateMidPoint(Point4D& a, Point4D& b) {
	Point4D res;

	res.mX = (a.mX + b.mX) / 2;
	res.mY = (a.mY + b.mY) / 2;
	res.mW = (a.mW + b.mW) / 2;

	return res;
}

cVoid BezierCurve::CubicBezier(Renderer& renderer, ProjectionPlane& plane, cWinColor color,
							   Point4D p0, Point4D p1, Point4D p2, Point4D p3) {
	struct SubCurve {
		Point4D p0, p1, p2, p3;
		SubCurve *next;
	} *head, *bp;
	Point4D p[5];
	cInt iHalfWidth = renderer.GetWidth() >> 1, iHalfHeight = renderer.GetHeight() >> 1;
	cFloat cs = plane.GetNearClippingPlane();
	head = new SubCurve();
	head->next = cNull;
	head->p0 = p0;
	head->p1 = p1;
	head->p2 = p2;
	head->p3 = p3;

	while (head != cNull) {
		for (SubCurve *ptr = head, *bptr = cNull; ptr != cNull;)	{
			// if current polygon is fully clipped
			if (ptr->p0.mW < cs && ptr->p1.mW < cs && ptr->p2.mW < cs && ptr->p3.mW < cs) {
				if (bptr != cNull) {
					bptr->next = ptr->next;
					delete ptr;
					ptr = bptr->next;
				} else {
					head = ptr->next;
					delete ptr;
					ptr = head;
				}
				continue;
			}

			// calculating new polygons
			p[0] = CalculateMidPoint(ptr->p0, ptr->p1);
			p[2] = CalculateMidPoint(ptr->p1, ptr->p2);
			p[4] = CalculateMidPoint(ptr->p2, ptr->p3);
			p[1] = CalculateMidPoint(p[0], p[2]);
			p[3] = CalculateMidPoint(p[2], p[4]);
			p[2] = CalculateMidPoint(p[1], p[3]);

			// stop condition
			if (StopCubic(ptr->p1, p[1], ptr->p2, p[3])) {
				if (ptr->p0.mW >= cs && ptr->p3.mW >= cs) {
					ptr->p0.mX = (ptr->p0.mX / ptr->p0.mW) + iHalfWidth; 
					ptr->p0.mY = (ptr->p0.mY / -ptr->p0.mW) + iHalfHeight;
					p[2].mX = (p[2].mX / p[2].mW) + iHalfWidth; 
					p[2].mY = (p[2].mY / -p[2].mW) + iHalfHeight;
					ptr->p3.mX = (ptr->p3.mX / ptr->p3.mW) + iHalfWidth; 
					ptr->p3.mY = (ptr->p3.mY / -ptr->p3.mW) + iHalfHeight;
					renderer.DrawROPLine(ptr->p0, p[2], color);
					renderer.DrawROPLine(p[2], ptr->p3, color);
				}

				if (bptr != cNull) {
					bptr->next = ptr->next;
					delete ptr;
					ptr = bptr->next;
				} else {
					head = ptr->next;
					delete ptr;
					ptr = head;
				}
				continue;
			}

			// adding new polygons
			bp = new SubCurve();
			bp->next = ptr->next;
			ptr->next = bp;

			bp->p3 = ptr->p3;
			ptr->p1 = p[0];
			ptr->p2 = p[1];
			ptr->p3 = p[2];
			bp->p0 = p[2];
			bp->p1 = p[3];
			bp->p2 = p[4];

			bptr = bp;
			ptr = bp->next;
		}
	}
}

cVoid BezierCurve::QuadricBezier(Renderer& renderer, ProjectionPlane& plane, cWinColor color,
								 Point4D p0, Point4D p1, Point4D p2) {
	struct SubCurve {
		Point4D p0, p1, p2;
		SubCurve *next;
	} *head, *bp;
	Point4D p[3];
	cInt iHalfWidth = renderer.GetWidth() >> 1, iHalfHeight = renderer.GetHeight() >> 1;
	cFloat cs = plane.GetNearClippingPlane();
	head = new SubCurve();
	head->next = cNull;
	head->p0 = p0;
	head->p1 = p1;
	head->p2 = p2;

	while (head != cNull) {
		for (SubCurve *ptr = head, *bptr = cNull; ptr != cNull;)	{
			// if current polygon is fully clipped
			if (ptr->p0.mW < cs && ptr->p1.mW < cs && ptr->p2.mW < cs)	{
				if (bptr != cNull) {
					bptr->next = ptr->next;
					delete ptr;
					ptr = bptr->next;
				} else {
					head = ptr->next;
					delete ptr;
					ptr = head;
				}
				continue;
			}

			// calculating new polygons
			p[0] = CalculateMidPoint(ptr->p0, ptr->p1);
			p[2] = CalculateMidPoint(ptr->p1, ptr->p2);
			p[1] = CalculateMidPoint(p[0], p[2]);

			// stop condition
			if (StopQuadric(ptr->p1, p[1]))	{
				// if points are clipped
				cBool b0 = ptr->p0.mW >= cs;
				cBool b1 = p[0].mW >= cs;
				cBool b2 = p[1].mW >= cs;
				cBool b3 = p[2].mW >= cs;
				cBool b4 = ptr->p2.mW >= cs;

				// normalize
				ptr->p0.mX = (ptr->p0.mX / ptr->p0.mW) + iHalfWidth; 
				ptr->p0.mY = (ptr->p0.mY / -ptr->p0.mW) + iHalfHeight;
				p[0].mX = (p[0].mX / p[0].mW) + iHalfWidth; 
				p[0].mY = (p[0].mY / -p[0].mW) + iHalfHeight;
				p[1].mX = (p[1].mX / p[1].mW) + iHalfWidth; 
				p[1].mY = (p[1].mY / -p[1].mW) + iHalfHeight;
				p[2].mX = (p[2].mX / p[2].mW) + iHalfWidth; 
				p[2].mY = (p[2].mY / -p[2].mW) + iHalfHeight;
				ptr->p2.mX = (ptr->p2.mX / ptr->p2.mW) + iHalfWidth; 
				ptr->p2.mY = (ptr->p2.mY / -ptr->p2.mW) + iHalfHeight;

				// draw lines
				if (b0 && b1) {
					renderer.DrawROPLine(ptr->p0, p[0], color);
				}
				if (b1 && b2) {
					renderer.DrawROPLine(p[0], p[1], color);
				}
				if (b2 && b3) {
					renderer.DrawROPLine(p[1], p[2], color);
				}
				if (b3 && b4) {
					renderer.DrawROPLine(p[2], ptr->p2, color);
				}

				if (bptr != cNull) {
					bptr->next = ptr->next;
					delete ptr;
					ptr = bptr->next;
				} else {
					head = ptr->next;
					delete ptr;
					ptr = head;
				}
				continue;
			}

			// adding new polygons
			bp = new SubCurve();
			bp->next = ptr->next;
			ptr->next = bp;

			bp->p2 = ptr->p2;
			ptr->p1 = p[0];
			ptr->p2 = p[1];
			bp->p0 = p[1];
			bp->p1 = p[2];

			bptr = bp;
			ptr = bp->next;
		}
	}
}

cBool BezierCurve::StopCubic(Point4D& a1, Point4D& b1, Point4D& a2, Point4D& b2) {
	cFloat fDx, fDy;

	fDx = a1.mX / a1.mW - b1.mX / b1.mW;
	fDy = a1.mY / a1.mW - b1.mY / b1.mW;
	if (Abs(fDx) < CURVE_EPS && Abs(fDy) < CURVE_EPS) {
		fDx = a2.mX / a2.mW - b2.mX / b2.mW;
		fDy = a2.mY / a2.mW - b2.mY / b2.mW;
		if (Abs(fDx) < CURVE_EPS && Abs(fDy) < CURVE_EPS) {
			return true;
		}
	}

	return false;
}

cBool BezierCurve::StopQuadric(Point4D& a1, Point4D& b1) {
	cFloat fDx, fDy;

	fDx = a1.mX / a1.mW - b1.mX / b1.mW;
	fDy = a1.mY / a1.mW - b1.mY / b1.mW;

	if (Abs(fDx * fDy) < CURVE_EPS) {
		return true;
	}

	return false;
}

cVoid BezierCurve::SwitchPolygonVisibility() {
	mIsPolygonVisible = !mIsPolygonVisible;
}

cVoid BezierCurve::SwitchPointsVisibility() {
	mArePointsVisible = !mArePointsVisible;
}

/*virtual*/ cVoid BezierCurve::DisplayInfo(Renderer& renderer) {
	cChar pcBuff [MAX_BUF_SIZE];
	cInt iLineNr = 0;

	sprintf_s(pcBuff, "Bezier Curve Info:");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		true, true, true, mPolygonColor);
	sprintf_s(pcBuff, "Nr of points :");	
	renderer.DrawString(H_TXT_SPACER, iLineNr * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "%3i", mNrOfPoints);	
	renderer.DrawString(H_TXT_SPACER * 13, iLineNr++ * V_TXT_SPACER, pcBuff, 
		true, true, true, Color::ToWinColor(WHITE));
	iLineNr++;
	sprintf_s(pcBuff, "[Ctrl + L] - Show/Hide Polygon");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "[Ctrl + V] - Show/Hide Points Coordinates");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "Space - Deselect Points");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "LMB - Select/Deselect Point");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "RMB - Add Point");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "DEL - Delete Selected Point");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "LMB & Drag - Move Selected Point");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
}

/*virtual*/ cChar* BezierCurve::GetName() {
	return "BezierCurve";
}